package com.youlu.campus.service.wechat.impl;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.mongodb.client.result.UpdateResult;
import com.youlu.campus.base.exception.AccountException;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.base.user.WechatLogoAuth;

import com.youlu.campus.component.QiniuUtil;
import com.youlu.campus.entity.SystemApolloConfig;
import com.youlu.campus.entity.SystemNickNameAndHeaderImage;
import com.youlu.campus.entity.UserOtherInfo;
import com.youlu.campus.entity.UserPO;
import com.youlu.campus.entity.VO.req.CheckMobileVO;
import com.youlu.campus.entity.VO.res.UserResponse;
import com.youlu.campus.entity.WechatH5UserPO;
import com.youlu.campus.entity.WechatMiniUserPO;
import com.youlu.campus.entity.domain.DecryptWechatMiniResult;
import com.youlu.campus.entity.domain.WechatMiniDecryptRequest;
import com.youlu.campus.entity.domain.WechatMiniProgramSession;
import com.youlu.campus.entity.dto.sms.SmsBaseInfoDTO;
import com.youlu.campus.service.sms.SmsServerService;
import com.youlu.campus.service.system.SystemApolloConfigService;
import com.youlu.campus.service.utils.CommonUtils;
import com.youlu.campus.service.utils.HttpClientUtils;
import com.youlu.campus.service.utils.WebTokenUtils;
import com.youlu.campus.service.visitor.UserInfoExtendService;
import com.youlu.campus.service.visitor.VisitorUserService;
import com.youlu.campus.service.wechat.SystemNickNameAndHeaderImageService;
import com.youlu.campus.service.wechat.WechatH5Service;
import com.youlu.campus.service.wechat.WechatMiniService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WechatMiniServiceImpl implements WechatMiniService {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private WechatH5Service wechatH5Service;

    @Autowired
    private SmsServerService smsServerService;

    @Autowired
    private UserInfoExtendService userInfoExtendService;


    public static String LOGIN_IN_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";

    public static String ACCESS_TOKEN_PRE_FIX = "com.luyou.access-token.appId:%s.openId:%s";

    public static String SESSION_KEY_PRE_FIX = "com.luyou.session-key.appId:%s.openId:%s";

    @Autowired
    private VisitorUserService visitorUserService;

    @Autowired
    private QiniuUtil qiniuUtil;

    @Autowired
    private SystemNickNameAndHeaderImageService systemNickNameAndHeaderImageService;
    @Autowired
    private SystemApolloConfigService systemApolloConfigService;


    @Override
    public WechatMiniUserPO findByOpenId(String appId, String openId) {
        Query query = new Query().addCriteria(Criteria.where("openId").is(openId).and("appId").is(appId));
        WechatMiniUserPO wechatMiniUserPO = mongoTemplate.findOne(query, WechatMiniUserPO.class);
        return wechatMiniUserPO;
    }

    @Override
    public UserResponse login(String appId, String code) {
        log.info("login minin login appId:{},code:{} ", appId, code);
        if (StringUtils.isBlank(appId) || StringUtils.isBlank(code)) {
            throw new BusinessException("微信小程序登陆参数错误");
        }
        //请求用户信息
        //TODO 这里先写死
        //String appId = "wx4cfdf786575c1a1a";
        String apps = "f46d00985abadfbe747a002f888a2d88";
        String url = String.format(LOGIN_IN_URL, appId, apps, code);
        String str = HttpClientUtils.getRestTemplate().getForObject(url, String.class);
        log.info("login 请求 url:{},result:{}", url, str);
        if (StringUtils.isBlank(str)) {
            throw new BusinessException("返回信息为空");
        }
        WechatMiniProgramSession wechatSession = JSON.parseObject(str, WechatMiniProgramSession.class);
        if (StringUtils.isBlank(wechatSession.getOpenId())) {
            throw new BusinessException("返回信息错误");
        }
        //查询用户信息是否存在
        Query query = new Query().addCriteria(Criteria.where("openId").is(wechatSession.getOpenId()));
        WechatMiniUserPO miniUserPO = mongoTemplate.findOne(query, WechatMiniUserPO.class);
        Date now = new Date();
        if (miniUserPO == null) {
            miniUserPO = new WechatMiniUserPO();
            miniUserPO.setCreatedTime(now);
            miniUserPO.setCheckedMobile(false);
        }
        miniUserPO.setAppId(appId);
        miniUserPO.setOpenId(wechatSession.getOpenId());
        miniUserPO.setUnionId(wechatSession.getUnionId());
        miniUserPO.setUpdatedTime(now);


        UserPO userPO = visitorUserService.findByMiniOpenId(wechatSession.getOpenId());
        if (Objects.isNull(userPO)) {
            CheckMobileVO checkMobileVO = new CheckMobileVO();
            checkMobileVO.setMiniOpenId(wechatSession.getOpenId());
            userPO = visitorUserService.createUser(checkMobileVO);
        }
        miniUserPO.setAccountInfoId(userPO.getId());
        mongoTemplate.save(miniUserPO);

        //保存sessionKey到redis
        this.setSessionKey(appId, wechatSession.getOpenId(), wechatSession.getSessionKey());
        this.setWebAccessToken(appId, wechatSession.getOpenId());

        UserResponse userResponse = new UserResponse();
        String token = WebTokenUtils.signToken(wechatSession.getOpenId());
        userResponse.setToken(token);
        userResponse.setOpenId(wechatSession.getOpenId());
        //token缓存
        String userToken = "user_token:" + token;
        redisTemplate.opsForValue().set(userToken, userPO.getId(), 7L, TimeUnit.DAYS);
        return userResponse;
    }

    @Override
    public WechatMiniUserPO decryptUserData(WechatMiniDecryptRequest decryptRequest, UserPO user) {
        log.info(":>>> 开始解密微信用户信息,decryptRequest:{}", JSON.toJSONString(decryptRequest));
        if (Objects.isNull(decryptRequest) || StringUtils.isBlank(decryptRequest.getOpenId()) || StringUtils.isBlank(decryptRequest.getAppId())) {
            log.error(":>>> 解密用户参数错误:openId、appId、等信息为空");
            throw new BusinessException("解密用户参数错误");
        }
        if (user == null) {
            log.info(":>>> 用户未登陆");
            throw new BusinessException("用户未登录~");
        }
        if (Objects.isNull(decryptRequest)) {
            log.error(":>>> 解密用户数据参数异常");
            throw new BusinessException("用户数据参数异常~");

        }
        decryptRequest.setOpenId(user.getOpenId());
        String key = this.getSessionKey(decryptRequest.getAppId(), decryptRequest.getOpenId());
        if (StringUtils.isBlank(key)) {
            log.error(":>>> 解密用户数据:{},{} sessionKey获取错误", decryptRequest.getAppId(), decryptRequest.getOpenId());
            throw new BusinessException("获取sessionKey失败，请重新登录");
        }
        String sessionKey = key;
        log.info("appId:{},openId:{},sessionKey：{}", decryptRequest.getAppId(), decryptRequest.getOpenId(), sessionKey);
        //解密之后的用户信息
        String decrypt = null;
        try {
            decrypt = CommonUtils.decrypt(decryptRequest.getEncryptedData(), sessionKey, decryptRequest.getIv(), "UTF" +
                    "-8");
            log.info(":>>> 解密后的数据为:{}", decrypt);
        } catch (Exception e) {
            AccountException ex = new AccountException("解析用户信息异常");
            log.error("异常 :{}", e);
            throw ex;
        }
        log.info("解析用户信息，结果为:{}", decrypt);
        if (StringUtils.isEmpty(decrypt)) {
            throw new AccountException("解析用户信息为空");
        }
        DecryptWechatMiniResult decryptData = JSON.parseObject(decrypt, DecryptWechatMiniResult.class);
        //
        Update update = new Update();
        boolean isAuth = "1".equals(decryptRequest.getType());
        Query query = new Query().addCriteria(Criteria.where("openId").is(decryptRequest.getOpenId()));
        if (isAuth) {
            boolean re = updateWechatLogo(decryptData, decryptRequest.getOpenId());
            if (re) {
                update.set("avatarUrl", decryptData.getAvatarUrl());
                update.set("nickName", decryptData.getNickname());
            }
            update.set("province", decryptData.getProvince());
            update.set("city", decryptData.getCity());
            update.set("authorized", true);
            update.set("binded", true);
            update.set("validLogo", true);
        } else {
            update.set("mobile", decryptData.getPhoneNumber());
            update.set("checkedMobile", true);
        }
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, WechatMiniUserPO.class);
        log.info(":>>>  更新用户基础数据:{}", updateResult.getModifiedCount());
        WechatMiniUserPO wechatMiniUserPO = new WechatMiniUserPO();
        BeanUtils.copyProperties(user, wechatMiniUserPO);
        if (isAuth) {
            wechatMiniUserPO.setBinded(true);
        } else {
            wechatMiniUserPO.setCheckedMobile(true);
        }
        if (updateResult.getModifiedCount() > 0) {
            try {
                user.setMobile(decryptData.getPhoneNumber());
                user.setCity(decryptData.getCity());
                user.setProvince(decryptData.getProvince());
                user.setAvatarUrl(decryptData.getAvatarUrl());
                user.setNickName(decryptData.getNickname());
                user.setH5SignUp(Boolean.TRUE);
                userInfoExtendService.saveOrUpdate(user);
            } catch (Exception e) {
                log.error("【更行用户扩展信息错误】-> {}", user.getId(), e);
            }
        }
        return wechatMiniUserPO;
    }

    public boolean updateWechatLogo(DecryptWechatMiniResult decryptData, String openId) {
        Query query = new Query().addCriteria(Criteria.where("openId").is(openId));
        WechatMiniUserPO userPO = mongoTemplate.findOne(query, WechatMiniUserPO.class);
        if (userPO == null) {
            return false;
        }
        // 已经上传到七牛的不需要更新了
        if (StringUtils.isNotBlank(userPO.getAvatarUrl()) && !userPO.getAvatarUrl().startsWith("https://thirdwx.qlogo.cn")) {
            return false;
        }
        // 授权如果是灰色头像
        if ("微信用户".equals(decryptData.getNickname()) || decryptData.getAvatarUrl().startsWith("https://mmbiz.qpic.cn")) {
            log.info("updateWechatLogo get gray log:{}", decryptData.getAvatarUrl());
            // 是否为空
            if (StringUtils.isBlank(userPO.getAvatarUrl())) {
                // 随机获取一张
                SystemNickNameAndHeaderImage image = systemNickNameAndHeaderImageService.getRandom();
                decryptData.setNickname(image.getNickName());
                decryptData.setAvatarUrl(image.getAvatarUrl());
                return true;
            }
            // 已经存在非七牛存储的，更新到七牛
            String uploadUrl = qiniuUtil.uploadFileByUrl(userPO.getAvatarUrl(), "", "wechat-", false, null);
            List<String> contents = Lists.newArrayList("1", "2");
            // 更新用户需要校验图片
            if (!contents.contains(uploadUrl)) {
                // 上传成功则替换
                decryptData.setAvatarUrl(uploadUrl);
            } else {
                decryptData.setAvatarUrl(userPO.getAvatarUrl());
            }
            decryptData.setNickname(userPO.getNickName());
            return true;
        }
        //非灰色头像，非七牛地址保存到七牛
        String uploadUrl = qiniuUtil.uploadFileByUrl(decryptData.getAvatarUrl(), "", "wechat-", false, null);
        List<String> contents = Lists.newArrayList("1", "2");
        // 更新用户需要校验图片
        if (!contents.contains(uploadUrl)) {
            decryptData.setAvatarUrl(uploadUrl);
        }
        return true;
    }

    @Override
    public UserResponse bindMobile(CheckMobileVO req) {
        log.info("bindMobile 绑定手机号:{}", JSON.toJSONString(req));
        SmsBaseInfoDTO dto = SmsBaseInfoDTO.builder().phone(req.getMobile()).code(req.getCode()).build();
        Pair<Boolean, String> re = smsServerService.checkVerifyCode(dto);
        log.info("bindMobile 用户:{} 验证手机号:{} 结果:{}", req.getUserPO().getId(), req.getMobile(), JSON.toJSONString(re));
        if (!re.getLeft()) {
            throw new BusinessException(re.getRight());
        }
        String openId = req.getUserPO().getOpenId();
        UserPO userPO = visitorUserService.find(req.getMobile());
        if (userPO == null) {
            userPO = visitorUserService.findByMiniOpenId(openId);
        } else {
            if (StringUtils.isNotBlank(userPO.getMiniOpenId()) && !openId.equals(userPO.getMiniOpenId())) {
                throw new BusinessException("手机号已绑定,请联系客服进行解绑");
            }
        }

        Query queryMini = new Query().addCriteria(Criteria.where("openId").is(req.getUserPO().getMiniOpenId()));
        WechatMiniUserPO wechatMiniUserPO = mongoTemplate.findOne(queryMini, WechatMiniUserPO.class);
        Date now = new Date();
        wechatMiniUserPO.setMobile(req.getMobile());
        wechatMiniUserPO.setCheckedMobile(true);
        wechatMiniUserPO.setUpdatedTime(now);
        wechatMiniUserPO.setCheckedMobileTime(now);
        wechatMiniUserPO.setAccountInfoId(userPO.getId());
        wechatMiniUserPO = mongoTemplate.save(wechatMiniUserPO);

        UserResponse userResponse = new UserResponse();
        BeanUtils.copyProperties(wechatMiniUserPO, userResponse);
        String token = WebTokenUtils.signToken(wechatMiniUserPO.getOpenId());
        userResponse.setToken(token);
        //token缓存
        String userToken = "user_token:" + token;
        redisTemplate.opsForValue().set(userToken, userPO.getId(), 7L, TimeUnit.DAYS);
        return userResponse;
    }

    @Override
    public String getWebAccessToken(String appId, String openId) {
        log.info(":>>> 开始获取微信小程序Web端 AccessToken:{},{}", appId, openId);
        Object o = redisTemplate.opsForValue().get(String.format(ACCESS_TOKEN_PRE_FIX, appId, openId));
        if (Objects.isNull(o)) {
            return null;
        }
        return (String) o;
    }

    @Override
    public void setWebAccessToken(String appId, String openId) {
        log.info(":>>> 开始设置微信小程序Web端 AccessToken:{},{}", appId, openId);
        redisTemplate.opsForValue().set(String.format(ACCESS_TOKEN_PRE_FIX, appId, openId),
                WebTokenUtils.signToken(openId), 30, TimeUnit.DAYS);
    }

    @Async
    @Override
    public void updateOtherInfo(UserOtherInfo u) {
        if (Objects.isNull(u) || StringUtils.isBlank(u.getUserId())) {
            log.error(":>>> 修改用户其他信息参数错误");
            return;
        }
        Query query = new Query().addCriteria(Criteria.where("accountInfoId").is(u.getUserId()));
        Update up = new Update();
        boolean doIt = false;
        if (StringUtils.isNotBlank(u.getNickName())) {
            up.set("nickName", u.getNickName());
            doIt = true;
        }
        if (StringUtils.isNotBlank(u.getAvatarUrl())) {
            up.set("avatarUrl", u.getAvatarUrl());
            doIt = true;
        }
        if (doIt) {
            up.set("updatedTime", new Date());
            UpdateResult updateResult = mongoTemplate.updateMulti(query, up, WechatMiniUserPO.class);
            log.info(":>>> 修改微信小程序用户信息结果:{},{}", u.getUserId(), updateResult.getModifiedCount());
        }

    }

    @Override
    public void uploadMiniWechatPic(WechatMiniUserPO wechatMiniUserPO) {
        String url = wechatMiniUserPO.getAvatarUrl();
        if (StringUtils.isBlank(url)) {
            if (isNewVersion()) {
                if (StringUtils.isNotBlank(wechatMiniUserPO.getUnionId())) {
                    // 微信h5有用微信h5的
                    WechatH5UserPO wechatH5UserPO = findSortWechatUser(wechatMiniUserPO.getUnionId());
                    if (wechatH5UserPO != null && StringUtils.isNotBlank(wechatH5UserPO.getAvatarUrl())) {
                        updateMiniLogo(wechatH5UserPO.getAvatarUrl(), wechatH5UserPO.getNickName(), wechatMiniUserPO.getId());
                        return;
                    }
                }
                // 随机获取一张系统图像
                SystemNickNameAndHeaderImage image = systemNickNameAndHeaderImageService.getRandom();
                updateMiniLogo(image.getAvatarUrl(), image.getNickName(), wechatMiniUserPO.getId());
            }
            return;
        }
        if (!url.startsWith("https://thirdwx.qlogo.cn")) {
            return;
        }
//        Config config = ConfigService.getAppConfig();
//        String cnf = config.getProperty("wechat.logo.auth.gray", "{}");
//        WechatLogoAuth wechatLogoAuth = JSON.parseObject(cnf, WechatLogoAuth.class);
        SystemApolloConfig systemApolloConfig = systemApolloConfigService.find();
        WechatLogoAuth wechatLogoAuth = systemApolloConfig.getWechatLogoAuth();
        // 是否开启更新头像
        if (!wechatLogoAuth.getUpdateLogoOpened()) {
            return;
        }
        List<String> contents = Lists.newArrayList("1", "2");
        // 更新用户需要校验图片
        String uploadUrl = qiniuUtil.uploadFileByUrl(url, "", "wechat-", true, wechatLogoAuth.getLogoMd5List());
        if (!contents.contains(uploadUrl)) {
            // 上传成功更新数据库头像地址
            updateMiniLogo(wechatMiniUserPO, uploadUrl, true);
            return;
        }
        // 上传失败不处理
        if ("2".equals(uploadUrl)) {
            log.info("uploadH5WechatPic fail openId:{}, id :{}", wechatMiniUserPO.getOpenId(), wechatMiniUserPO.getId());
            return;
        }
        // 随机获取一张系统图像
        SystemNickNameAndHeaderImage image = systemNickNameAndHeaderImageService.getRandom();
        updateMiniLogo(image.getAvatarUrl(), image.getNickName(), wechatMiniUserPO.getId());
    }

    public boolean isNewVersion() {
//        Config config = ConfigService.getAppConfig();
//        String cnf = config.getProperty("wechat.mini.base", "{}");
//        WechatMiniBase wechatMiniBase = JSON.parseObject(cnf, WechatMiniBase.class);
        /*SystemApolloConfig systemApolloConfig = systemApolloConfigService.find();
        WechatMiniBase wechatMiniBase = systemApolloConfig.getWechatMiniBase();
        return "v1".equals(wechatMiniBase.getVersion());*/
        return true;
    }

    public List<WechatH5UserPO> getSortWechatUserList(String unionId) {
        List<WechatH5UserPO> wechatList = wechatH5Service.selectByUnionId(unionId);
        return wechatList.stream().sorted((o1, o2) -> {
            if (StringUtils.isBlank(o1.getAvatarUrl())) {
                return 1;
            }
            if (o1.getAvatarUrl().startsWith("https://thirdwx.qlogo.cn")) {
                return 0;
            }
            return -1;
        }).collect(Collectors.toList());
    }

    public WechatH5UserPO findSortWechatUser(String unionId) {
        List<WechatH5UserPO> wechatList = getSortWechatUserList(unionId);
        if (CollectionUtils.isEmpty(wechatList)) {
            return null;
        }
        return wechatList.stream().findFirst().orElse(null);
    }

    public void updateMiniLogo(WechatMiniUserPO wechatMiniUserPO, String uploadUrl, boolean up) {
        Query query = new Query().addCriteria(Criteria.where("id").is(wechatMiniUserPO.getId()));
        Update update = new Update();
        if (up) {
            update.set("avatarUrl", uploadUrl);
            update.set("validLogo", true);
        } else {
            update.set("validLogo", false);
            update.set("avatarUrl", "");
        }
        mongoTemplate.updateFirst(query, update, WechatMiniUserPO.class);
    }

    public void updateMiniLogo(String avatarUrl, String nickName, String id) {
        Query query = new Query().addCriteria(Criteria.where("id").is(id));
        Update update = new Update();
        update.set("avatarUrl", avatarUrl);
        update.set("nickName", nickName);
        update.set("validLogo", true);
        mongoTemplate.updateFirst(query, update, WechatMiniUserPO.class);
    }

    private String getSessionKey(final String appId, final String openId) {
        Object v = redisTemplate.opsForValue().get(getSessionKeyK(appId, openId));
        if (Objects.isNull(v)) {
            return null;
        }
        return v.toString();
    }

    private void setSessionKey(final String appId, final String openId, final String sessionKey) {
        redisTemplate.opsForValue().set(getSessionKeyK(appId, openId), sessionKey, 30, TimeUnit.DAYS);
    }

    private String getSessionKeyK(final String appId, final String openId) {
        log.info(":>>> 微信小程序:{},openId:{} sessionKey k:{}", appId, openId, String.format(SESSION_KEY_PRE_FIX, appId, openId));
        return String.format(SESSION_KEY_PRE_FIX, appId, openId);
    }

    @Override
    public void updateAccountId(String id, String accountId) {
        Query query = new Query().addCriteria(Criteria.where("id").is(id));
        Update update = new Update();
        update.set("accountInfoId", accountId);
        mongoTemplate.updateFirst(query, update, WechatMiniUserPO.class);
    }
}
